﻿namespace ClassyBlog.DataAccess.Specs.Unit
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    using Machine.Specifications;
    using NSubstitute;

    using Infrastructure;

    [Subject(typeof(LazyArticleLoader<Article>))]
    public class when_creating_lazy_article_loader_with_blank_path
    {
        static Exception exception;

        Because of = () => exception =
            Catch.Exception(() =>
                new LazyArticleLoader<Article>(
                    string.Empty, Substitute.For<IFileSystem>()));

        It should_throw_exception = () =>
            exception.ShouldBeOfType<ArgumentException>();
    }

    [Subject(typeof(LazyArticleLoader<Article>))]
    public class when_creating_lazy_article_loader_with_null_file_system
    {
        const string Path = @"c:\inetpub\wwwroot\classyblog\app_data\" +
                            @"\articles\2010-04-02-17-30-00_hello-world.md";

        static Exception exception;

        Because of = () => exception =
            Catch.Exception(() => new LazyArticleLoader<Article>(Path, null));

        It should_throw_exception = () => exception.ShouldBeOfType<ArgumentNullException>();
    }

    [Subject(typeof(LazyArticleLoader<Article>))]
    public class when_creating_lazy_article_loader_with_null_property_setters
    {
        const string Path = @"c:\inetpub\wwwroot\classyblog\app_data\" +
                            @"\articles\2010-04-02-17-30-00_hello-world.md";

        static Exception exception;

        Because of = () => exception =
            Catch.Exception(() =>
                new LazyArticleLoader<Article>(
                    Path, Substitute.For<IFileSystem>(), null));

        It should_throw_exception = () =>
            exception.ShouldBeOfType<ArgumentNullException>();
    }

    public abstract class LazyArticleLoaderSpec
    {
        protected const string Title = "Hello world";
        protected const string MetaKeywords = "test, sample, dummy";
        protected const string MetaDescription = "This is a dummy meta " + 
                                                 "description";

        protected const string Author = "Kazi Manzur Rashid";
        protected const bool AllowComments = true;
        protected const string Body = "This is body of the hello world " +
                                      "article.";

        protected static Article article;
        protected static IFileSystem fileSystem;
        protected static LazyArticleLoader<Article> articleLoader;

        const string Path = @"c:\inetpub\wwwroot\classyblog\app_data\" +
                            @"\articles\2010-04-02-17-30-00_hello-world.md";

        Establish context = () =>
        {
            fileSystem = Substitute.For<IFileSystem>();
            articleLoader = new LazyArticleLoader<Article>(Path, fileSystem);
            article = new Article();
        };
    }

    [Subject(typeof(LazyArticleLoader<Article>))]
    public class when_trying_to_load_null_article : LazyArticleLoaderSpec
    {
        static Exception exception;

        Because of = () => exception =
            Catch.Exception(() => articleLoader.Load(null));

        It should_throw_exception = () =>
            exception.ShouldBeOfType<ArgumentNullException>();
    }

    [Subject(typeof(LazyArticleLoader<Article>))]
    public class when_article_is_loaded_with_all_known_properties :
        LazyArticleLoaderSpec
    {
        Establish context = () =>
        {
            var newLine = Environment.NewLine;

            var content = "Title: " + Title + newLine +
                          "MetaKeywords: " + MetaKeywords + newLine +
                          "MetaDescription: " + MetaDescription + newLine +
                          "Author: " + Author + newLine +
                          "AllowComments: " +
                          AllowComments.ToString().ToLowerInvariant() +
                          newLine +
                          newLine +
                          Body;

            fileSystem.ReadAll(Arg.Any<string>()).Returns(content);
        };

        Because of = () => articleLoader.Load(article);

        It should_have_same_title = () => article.Title.ShouldEqual(Title);

        It should_have_same_meta_keywords = () =>
            article.MetaKeywords.ShouldEqual(MetaKeywords);

        It should_have_same_meta_description = () =>
            article.MetaDescription.ShouldEqual(MetaDescription);

        It should_have_same_author = () => article.Author.ShouldEqual(Author);

        It should_have_same_allow_comments = () =>
            article.AllowComments.ShouldEqual(AllowComments);

        It should_have_same_body = () => article.Body.ShouldEqual(Body);
    }

    [Behaviors]
    public class ArticleWithoutMeta
    {
        protected static Article article;

        It should_not_have_title = () => article.Title.ShouldBeNull();

        It should_not_have_meta_keywords = () =>
            article.MetaKeywords.ShouldBeNull();

        It should_not_have_meta_description = () =>
            article.MetaDescription.ShouldBeNull();

        It should_not_have_author = () => article.Author.ShouldBeNull();

        It should_have_allow_comments_as_false = () =>
            article.AllowComments.ShouldBeFalse();
    }

    [Subject(typeof(LazyArticleLoader<Article>))]
    public class When_article_is_loaded_without_meta_data :
        LazyArticleLoaderSpec
    {
        Establish context = () =>
            fileSystem.ReadAll(Arg.Any<string>()).Returns(Body);

        Because of = () =>
            articleLoader.Load(article);

        It should_treat_the_whole_content_as_body = () =>
            article.Body.ShouldEqual(Body);

        Behaves_like<ArticleWithoutMeta> noMetadata;
    }

    [Subject(typeof(LazyArticleLoader<Article>))]
    public class when_article_is_loaded_with_no_meta_data_marker :
        LazyArticleLoaderSpec
    {
        const string InvalidBody = "Title: foo" + Body;

        Establish context = () =>
            fileSystem.ReadAll(Arg.Any<string>()).Returns(InvalidBody);

        Because of = () => articleLoader.Load(article);

        It should_treat_the_whole_content_as_body = () =>
            article.Body.ShouldEqual(InvalidBody);

        Behaves_like<ArticleWithoutMeta> noMetadata;
    }

    [Subject(typeof(LazyArticleLoader<Article>))]
    public class when_article_is_loaded_with_empty_meta_data :
        LazyArticleLoaderSpec
    {
        static readonly string EmptyMetaWithBody = Environment.NewLine +
                                                   Environment.NewLine +
                                                   Body;

        Establish context = () =>
            fileSystem.ReadAll(Arg.Any<string>()).Returns(EmptyMetaWithBody);

        Because of = () => articleLoader.Load(article);

        It should_only_load_body = () => article.Body.ShouldEqual(Body);

        Behaves_like<ArticleWithoutMeta> noMetadata;
    }

    [Subject(typeof(LazyArticleLoader<Article>))]
    public class when_article_is_loaded_with_invalid_meta_data :
        LazyArticleLoaderSpec
    {
        static readonly string InvalidBody = "\tTitle: foo \t\t" +
                                             "MetaKeywords: foo, bar" +
                                             Environment.NewLine +
                                             Environment.NewLine +
                                             Body;

        static Exception exception;

        Establish context = () =>
            fileSystem.ReadAll(Arg.Any<string>()).Returns(InvalidBody);

        Because of = () => exception =
            Catch.Exception(() => articleLoader.Load(article));

        It should_throw_exception = () =>
            exception.ShouldBeOfType<InvalidOperationException>();
    }

    [Subject(typeof(LazyArticleLoader<Article>))]
    public class when_article_is_loaded_with_incorrect_meta_data :
        LazyArticleLoaderSpec
    {
        static readonly string EmptyMetaWithBody = " - foo" +
                                                   Environment.NewLine +
                                                   Environment.NewLine +
                                                   Body;

        Establish context = () =>
            fileSystem.ReadAll(Arg.Any<string>()).Returns(EmptyMetaWithBody);

        Because of = () => articleLoader.Load(article);

        It should_only_load_body = () => article.Body.ShouldEqual(Body);

        Behaves_like<ArticleWithoutMeta> noMetadata;
    }

    [Behaviors]
    public class ArticleWithMatchingHandler
    {
        protected static bool matching;

        It should_call_the_provided_callback = () => matching.ShouldBeTrue();
    }

    public abstract class LazyArticleWithListSpec
    {
        protected const string Body = "This is the dummy body.";
        protected static IFileSystem fileSystem;
        protected static bool matching;

        const string Path = @"c:\inetpub\wwwroot\classyblog\app_data\" +
                            @"\articles\2010-04-02-17-30-00_hello-world.md";

        static readonly Action<Article, string, object> tagsHandler =

            (a, p, v) =>
        {
            var list = ((IEnumerable<string>)v).ToList();

            if (list[0].Equals("Tag1") && list[1].Equals("Tag2"))
            {
                matching = true;
            }
        };

        static Article article;
        static LazyArticleLoader<Article> articleLoader;

        Establish context = () =>
        {
            fileSystem = Substitute.For<IFileSystem>();
            articleLoader = new LazyArticleLoader<Article>(
                Path,
                fileSystem,
                new Dictionary<string, Action<Article, string, object>>
                    {
                        { "Tags", tagsHandler }
                    });

            article = new Article();
        };

        Because of = () => articleLoader.Load(article);
    }

    [Subject(typeof(LazyArticleLoader<Article>))]
    public class when_article_is_loaded_with_regular_list :
        LazyArticleWithListSpec
    {
        Establish context = () =>
        {
            var newLine = Environment.NewLine;

            var content = "Tags: " + newLine +
                          "  - Tag1" + newLine +
                          "  - Tag2" + newLine +
                          newLine +
                          Body;

            fileSystem.ReadAll(Arg.Any<string>()).Returns(content);
        };

        Behaves_like<ArticleWithMatchingHandler> matchedHandler;
    }

    [Subject(typeof(LazyArticleLoader<Article>))]
    public class when_article_is_loaded_with_inline_list :
        LazyArticleWithListSpec
    {
        Establish context = () =>
        {
            var newLine = Environment.NewLine;

            var content = "Tags: [Tag1, Tag2]" + newLine +
                          newLine +
                          Body;

            fileSystem.ReadAll(Arg.Any<string>()).Returns(content);
        };

        Behaves_like<ArticleWithMatchingHandler> matchedHandler;
    }

    public abstract class LazyArticleWithHashSpec
    {
        protected const string Body = "This is the dummy body.";

        protected static IFileSystem fileSystem;
        protected static bool matching;

        const string Path = @"c:\inetpub\wwwroot\classyblog\app_data\" +
                            @"\articles\2010-04-02-17-30-00_hello-world.md";

        static readonly Action<Article, string, object> hashHandler = (a, p, v) =>
        {
            var hash = (IDictionary<string, string>)v;

            if (hash["First"].Equals("Value1") &&
                hash["Second"].Equals("Value2"))
            {
                matching = true;
            }
        };

        static Article article;
        static LazyArticleLoader<Article> articleLoader;

        Establish context = () =>
        {
            fileSystem = Substitute.For<IFileSystem>();
            articleLoader = new LazyArticleLoader<Article>(
                Path,
                fileSystem,
                new Dictionary<string, Action<Article, string, object>>
                    {
                        { "Pairs", hashHandler }
                    });

            article = new Article();
        };

        Because of = () => articleLoader.Load(article);
    }

    [Subject(typeof(LazyArticleLoader<Article>))]
    public class when_article_is_loaded_with_regular_hash :
        LazyArticleWithHashSpec
    {
        Establish context = () =>
        {
            var newLine = Environment.NewLine;

            var content = "Pairs: " + newLine +
                          "  First: Value1" + newLine +
                          "  Second: Value2" + newLine +
                          newLine +
                          Body;

            fileSystem.ReadAll(Arg.Any<string>()).Returns(content);
        };

        Behaves_like<ArticleWithMatchingHandler> matchedHandler;
    }

    [Subject(typeof(LazyArticleLoader<Article>))]
    public class when_article_is_loaded_with_inline_hash :
        LazyArticleWithHashSpec
    {
        Establish context = () =>
        {
            var newLine = Environment.NewLine;

            var content = "Pairs: { First: Value1, Second: Value2 }" +
                          newLine +
                          newLine +
                          Body;

            fileSystem.ReadAll(Arg.Any<string>()).Returns(content);
        };

        Behaves_like<ArticleWithMatchingHandler> matchedHandler;
    }

    [Subject(typeof(LazyArticleLoader<Article>))]
    public class when_article_is_loaded_with_unknown_meta_data
    {
        const string Path = @"c:\inetpub\wwwroot\classyblog\app_data\" +
                            @"\articles\2010-04-02-17-30-00_hello-world.md";

        static IFileSystem fileSystem;
        static Article article;
        static LazyArticleLoader<Article> articleLoader;

        Establish context = () =>
        {
            fileSystem = Substitute.For<IFileSystem>();

            var newLine = Environment.NewLine;

            var content = "Foo: Bar" + newLine +
                          newLine + 
                          "This is a dummy body";

            fileSystem.ReadAll(Arg.Any<string>()).Returns(content);

            articleLoader = new LazyArticleLoader<Article>(Path, fileSystem);
            article = new Article();
        };

        Because of = () => articleLoader.Load(article);

        It should_have_unknown_property_as_dynamic = () => article.GetDynamicProperty("Foo").ShouldEqual("Bar");
    }
}